package protocol

import (
	"errors"
	"net"
	"runtime/debug"
	"sync"
	"sync/atomic"

	log "github.com/sirupsen/logrus"
)

var (
	ErrConnClosing = errors.New("use of closed network connection")
)

type Client struct {
	id         int64
	server     Server
	conn       net.Conn
	addrString string
	closeOnece sync.Once
	closeFlag  int32
	closeChan  chan struct{}
	recv       chan interface{}
	secv       chan interface{}
	callBack   ClientCallBack
}

type ClientCallBack interface {
	OnConnect(*Client) bool
	OnMessage(*Client, interface{})
	OnClose(*Client)
}

func newClient(server Server, conn net.Conn) *Client {
	return &Client{
		server:     server,
		conn:       conn,
		addrString: conn.RemoteAddr().String(),
		closeChan:  make(chan struct{}),
		recv:       make(chan interface{}, 20),
		secv:       make(chan interface{}, 20),
		callBack:   server.ClientCallBack(),
		closeFlag:  0,
	}
}

func NewClient(server Server, conn net.Conn) *Client {
	return newClient(server, conn)
}

func (c *Client) Close() {
	c.closeOnece.Do(func() {
		// 先关掉 读写处理协程
		close(c.closeChan)
		//关闭写数据管道
		close(c.secv)
		// 清空写管道数据
		c.flushWrite()
		atomic.StoreInt32(&c.closeFlag, 1)
		c.server.RemoveClient(c.id)
		c.callBack.OnClose(c)
		// 关闭套接字
		c.conn.Close()
	})
}

func (c *Client) flushWrite() {
	for p := range c.secv {
		if c.IsClose() || p == nil {
			return
		}
		b, err := c.Marshal(p)
		if err != nil {
			log.WithFields(log.Fields{
				"data": err.Error(),
			}).Error("client write Marshal err")
			break
		}
		if err := c.Write(b); err != nil {
			log.WithFields(log.Fields{
				"data": err.Error(),
			}).Error("client write err")
			return
		}
	}
}

func (c *Client) GetConn() net.Conn {
	return c.conn
}

func (c *Client) GetAddrString() string {
	return c.addrString
}

func (c *Client) GetServer() Server {
	return c.server
}

func (c *Client) ClientId() int64 {
	return c.id
}

func (c *Client) IsClose() bool {
	if c == nil {
		return true
	}
	return atomic.LoadInt32(&c.closeFlag) == 1
}

func (c *Client) WriteMsg(p interface{}) (err error) {
	if c.IsClose() {
		return ErrConnClosing
	}
	if p != nil && c.secv != nil {
		c.secv <- p
		return nil
	}
	return ErrConnClosing
}

func (c *Client) Write(b [][]byte) (err error) {
	if err := c.server.Protocol().Write(c.conn, b...); err != nil {
		return err
	}
	return
}
func (c *Client) Read() (r []byte, err error) {
	return c.server.Protocol().Read(c.conn)
}

func (c *Client) Unmarshal(data []byte) (r interface{}, err error) {
	return c.server.Protocol().Unmarshal(data)
}

func (c *Client) Marshal(msg interface{}) ([][]byte, error) {
	return c.server.Protocol().Marshal(msg)
}

func (c *Client) RemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}

func (c *Client) Do() {
	if !c.callBack.OnConnect(c) {
		return
	}
	c.id = c.server.ClientIDSequence()
	c.server.AddClient(c.id, c)
	c.server.WaitGroupFunc(c.readLoop)
	c.server.WaitGroupFunc(c.writeLoop)
	c.server.WaitGroupFunc(c.handleLoop)
}

func (c *Client) readLoop() {
	defer func() {
		if err := recover(); err != nil {
			log.WithFields(log.Fields{
				"data": string(debug.Stack()),
			}).Error("client readLoop")
		}
		c.Close()
	}()

	for {
		select {
		case <-c.closeChan:
			return
		default:
		}

		b, err := c.Read()
		if err != nil {
			mdata := make(map[string]interface{}, 0)
			mdata["error"] = err.Error()
			mdata["id"] = c.id
			mdata["ip"] = c.addrString
			log.WithFields(log.Fields{
				"data": mdata,
			}).Error("client read err")
			return
		}
		r, err := c.Unmarshal(b)
		if err != nil {
			log.WithFields(log.Fields{
				"data": err.Error(),
			}).Error("client read Unmarshal err")
			return
		}
		c.recv <- r
	}
}

func (c *Client) writeLoop() {
	defer func() {
		if err := recover(); err != nil {
			log.WithFields(log.Fields{
				"data": string(debug.Stack()),
			}).Error("client writeLoop")
		}
		c.Close()
	}()
	for {
		select {
		case <-c.closeChan:
			return
		case p := <-c.secv:
			if c.IsClose() {
				return
			}
			b, err := c.Marshal(p)
			if err != nil {
				log.WithFields(log.Fields{
					"data": err.Error(),
				}).Error("client write Marshal err")
				break
			}
			if err := c.Write(b); err != nil {
				log.WithFields(log.Fields{
					"data": err.Error(),
				}).Error("client write err")
				return
			}
		}
	}
}

func (c *Client) handleLoop() {
	defer func() {
		if err := recover(); err != nil {
			log.WithFields(log.Fields{
				"data": string(debug.Stack()),
			}).Error("client handleLoop err:", err)
		}
		c.Close()
	}()
	for {
		select {
		case <-c.closeChan:
			return
		case p := <-c.recv:
			c.callBack.OnMessage(c, p)
		}
	}
}
